package view;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.MemoryImageSource;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import model.CollisionUtils;
import model.Sprite;
import model.SpriteCollection;

import controller.Events;
import controller.TSWallAppertureCalculator;
import controller.TunnelShiftSpriteFactory;

public class GameHandler extends Observable implements ScreenHandler,KeyListener{

	
	static GameHandler singleton;
	
	public static GameHandler getInstance(Applet applet){
		synchronized(GameHandler.class){
			if(singleton == null){
				return new GameHandler(applet);
			}else{
				return singleton;
			}
		}
	}
	
	//dev-defined 
		int wallSpeed = 1;
		int heroSpeed = 3;
		int heartBeatInterval = 1000/100;
		
		//aperture Calculator base definition
		int moveSpeed = 2; //speed of the aperture displacement
		int openingWidth = 25; //height of the aperture (in % of the total height)
		int openingVariation = 5; //aperture width variation (in % of the total height)
	
    
	
	//program-defined
	int wallWidth = TunnelShiftSpriteFactory.getWallWidth();
    int wallDecal=0;
    int[] initHeroPos={10,180};
    boolean dirUp=false;
    boolean dirDown=false;
    boolean dirLeft=false;
    boolean dirRight=false;
	
	//attributes
	Set<Sprite> spritesToRender = new HashSet<Sprite>(); 
	Sprite hero;
	SpriteCollection obstacles  = new SpriteCollection();
	Cursor transparentCursor;
	Applet ts;
    Timer heartBeat;
    TSWallAppertureCalculator appertureCalculator;

	
	
	private GameHandler(Applet applet){
		
		synchronized(GameHandler.class){
			singleton = this;
		}
		
		this.ts=applet;
		
		int[] pixels = new int[16 * 16];
        Image image = Toolkit.getDefaultToolkit().createImage(
                new MemoryImageSource(16, 16, pixels, 0, 16));
        transparentCursor =
                Toolkit.getDefaultToolkit().createCustomCursor
                     (image, new Point(0, 0), "invisibleCursor");
        hideMouseCursor();
        
	}

	private void nextStep() {
		// is called every 100th of a second to calculates the "frame" thing ...
		movePlayer();		
		forwardWall();		
		findCollisions();	
	}
	private void findCollisions(){
		try{
			if(CollisionUtils.intersects(hero, obstacles)){
				reset();
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void forwardWall(){	
		//make the walls move...
		obstacles.move(0-wallSpeed, 0);
		obstacles.refresh();
		wallDecal+=wallSpeed;
		//tant que le prochain decalage (wallDecal+wallSpeed) est plus grand que la largeur d'un mur (wallWidth)
		// --> on rajoute un mur et en réduit le décalage.	
		while(wallDecal+wallSpeed>wallWidth){
			addWall();
		}
		
	}

	private void movePlayer(){
		//make the hero move...
		if(dirUp || dirDown || dirLeft || dirRight){
			
			int maxWidth = ts.getWidth()-hero.getCollisionMap().getWidth();
			int maxHeight = ts.getHeight()-hero.getCollisionMap().getHeight();
			
			int[] pos = hero.getPosition();
			if(dirUp)pos[1]-=heroSpeed;
			if(dirDown)pos[1]+=heroSpeed;
			if(dirLeft)pos[0]-=heroSpeed;
			if(dirRight)pos[0]+=heroSpeed;

			if(pos[1]<0)pos[1]=0;
			if(pos[0]<0)pos[0]=0;
			if(pos[1]>maxWidth)pos[1]=maxWidth;
			if(pos[0]>maxHeight)pos[0]=maxHeight;
		}
		
	}
	
	
	@Override
	public void paint(Image img) {
		Graphics g = img.getGraphics();
		g.setColor( Color.black ); 
		g.fillRect( 0, 0, img.getWidth(null), img.getHeight(null) );
		
		renderSprites(g);
		
	}

	private void renderSprites(Graphics g){
		for (Iterator<Sprite> it = spritesToRender.iterator(); it.hasNext();) {
			Sprite s = it.next();
			
			if(s.getPosition()[0]+s.getCollisionMap().getWidth()<0){
				  obstacles.removeSprite(s);
				  it.remove();
			}else{
//to view the sprites				
				  boolean painted = g.drawImage(s.getImage().getImage(),s.getPosition()[0],s.getPosition()[1],null);
				
//to view the collMaps (simple as green) 
				  //boolean painted = g.drawImage(s.getCollisionMapImage().getImage(),s.getPosition()[0],s.getPosition()[1],null);
				  if(!painted)System.out.println("an image is not fully painted O_o");
			}
		}	
//to view the collMaps (composite as red)
		//g.drawImage(obstacles.getCollisionMapImage().getImage(),obstacles.getPosition()[0],obstacles.getPosition()[1],null);
	}
	private void hideMouseCursor(){
		ts.setCursor(transparentCursor);
	}
	/*private void showMouseCursor(){
		ts.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
	}*/
	
	
	@Override
	public void repaint(Image i) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void pause() {
		// TODO Auto-generated method stub
		
	}


	@Override
	public void reset() {
		// TODO Auto-generated method stub
		spritesToRender = new HashSet<Sprite>(); 
		obstacles = new SpriteCollection();
		hero = TunnelShiftSpriteFactory.getHero();
		hero.setPosition(initHeroPos[0],initHeroPos[1]);
		spritesToRender.add(hero);
		wallDecal=0;
		dirRight=dirLeft=dirDown=dirUp=false;
		appertureCalculator = new TSWallAppertureCalculator(ts.getHeight(),moveSpeed,openingWidth,openingVariation);
	}

	@Override
	public void start() {
		// TODO Auto-generated method stub
		heartBeat = new Timer();
		heartBeat.scheduleAtFixedRate(new ComputingTask(), heartBeatInterval, heartBeatInterval);
	}

	@Override
	public void stop() {
		// TODO Auto-generated method stub
		heartBeat.cancel();
	}

	@Override
	public void keyPressed(KeyEvent ke) {
		
		switch (ke.getKeyCode()) {
			case KeyEvent.VK_ESCAPE:
					setChanged();
					notifyObservers(Events.GAME_OVER);
				break;
			case KeyEvent.VK_UP:
					dirUp=true;
				break;
			case KeyEvent.VK_DOWN:
					dirDown=true;
				break;
			case KeyEvent.VK_LEFT:
					dirLeft=true;
				break;
			case KeyEvent.VK_RIGHT:
					dirRight=true;
				break;
				
			default:
				break;
		}
		
	}
	
	@Override
	public void keyReleased(KeyEvent ke) {
		
		switch (ke.getKeyCode()) {
			case KeyEvent.VK_ESCAPE:
					setChanged();
					notifyObservers(Events.GAME_OVER);
				break;
			case KeyEvent.VK_UP:
					dirUp=false;
				break;
			case KeyEvent.VK_DOWN:
					dirDown=false;
				break;
			case KeyEvent.VK_LEFT:
					dirLeft=false;
				break;
			case KeyEvent.VK_RIGHT:
					dirRight=false;
				break;
				
			default:
				break;
		}
	}

	@Override
	public void keyTyped(KeyEvent ke) {
		// TODO Auto-generated method stub

	}


	

	private void addWall(){
		//Set<Sprite> wall = TunnelShiftSpriteFactory.getCompleteWall(ts.getSize().width,ts.getSize().height);
		List<int[]> wallParts = appertureCalculator.getNextAperture();
		Set<Sprite> wall = TunnelShiftSpriteFactory.getPartialWall(ts.getSize().width,ts.getSize().height,wallParts);
		obstacles.addAll(wall);
		spritesToRender.addAll(wall);
		wallDecal-=wallWidth;
	}
	
	
	private class ComputingTask extends TimerTask{
		
		public void run() {
			// TODO Auto-generated method stub
			try{
				nextStep();
			}catch(Exception e){
				
			}
		}
	}
	
}
